package cn.xy.base.server.aspect;

import cn.xy.commons.core.Result;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 控制层切面
 *
 *  @author xy
 */
@Slf4j
@Aspect
@Component
public class ControllerAspect {
    private static final String TRACE_ID = "traceId";

    private final ThreadLocal<Long> timeLocal = new ThreadLocal<>();
    private final ThreadLocal<String> methodLocal = new ThreadLocal<>();

    @Pointcut("execution(public * *..*.controller..*.*(..))")
    public void controllerMethod() {
        // empty function
    }

    @Before("controllerMethod()")
    public void doBefore(JoinPoint joinPoint) {
        timeLocal.set(System.currentTimeMillis());
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            methodLocal.set(request.getRequestURI());
        }
    }

    @Around("controllerMethod()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //添加 MDC
        String traceId = IdWorker.get32UUID();
        MDC.put(TRACE_ID, traceId);
        Object result = point.proceed();
        if (result instanceof Result) {
            ((Result) result).setTraceId(traceId);
        }
        //移除 MDC
        MDC.remove(TRACE_ID);
        return result;
    }

    @AfterReturning(returning = "ret", pointcut = "controllerMethod()")
    public void doAfterReturning(Object ret) {
        long costTime = System.currentTimeMillis() - timeLocal.get();
        log.info("request[{}] done, cost: {}ms", methodLocal.get(), costTime);

        methodLocal.remove();
        timeLocal.remove();
    }

    @AfterThrowing(throwing = "ex", pointcut = "controllerMethod()")
    public void doAfterThrowing(Exception ex) {
        long costTime = System.currentTimeMillis() - timeLocal.get();
        log.error("request[{}] error, cost: {}ms", methodLocal.get(), costTime);

        methodLocal.remove();
        timeLocal.remove();
    }
}
